import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.net.Socket;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Calendar;
import java.util.GregorianCalendar;

import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SealedObject;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;


public class Client {

	public PublicKey bankPublicKey = todo;
	
	public Client() {
		
	}
	
	public SealedObject encryptAndStoreBankInfo(String firstName, String lastName,
			long cardNumber, int cvvNumber,
			Calendar expirationDate) {
		SecretKey key = generateKey();
		try {
			BankInformation bankInfo = new BankInformation(firstName, lastName, 
					cardNumber, cvvNumber, expirationDate);
			FileOutputStream fos = new FileOutputStream("bank_information");
			ObjectOutputStream oos = new ObjectOutputStream(fos);
			oos.writeObject(BankInformation.encrypt(bankInfo, key));
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return encryptKey(key, bankPublicKey);
	}

	public SealedObject encryptKey(SecretKey key, PublicKey bankPublicKey) {
		SealedObject encryptedKey = null;
		try {
			Cipher cipher = Cipher.getInstance(bankPublicKey.getAlgorithm());
			encryptedKey = new SealedObject(key, cipher);
		} catch (NoSuchAlgorithmException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchPaddingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalBlockSizeException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return encryptedKey;
	}

	public SecretKey decryptKey(SealedObject encryptedKey, PrivateKey bankPrivateKey) {
		SecretKey decryptedKey = null;
		try {
			decryptedKey = (SecretKey) encryptedKey.getObject(bankPrivateKey);
		} catch (InvalidKeyException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchAlgorithmException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return decryptedKey;
	}
	
	public SecretKey generateKey() {
		SecretKey key = null;
		try {
			KeyGenerator generator = KeyGenerator.getInstance("AES");
			generator.init(new SecureRandom());
			key = generator.generateKey();
		} catch (NoSuchAlgorithmException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return key;
	}
	
	private Socket s;
	private OutputStream os;
	private InputStream is;
	private ObjectOutputStream oos;
	private ObjectInputStream ois;
	public void connectToPOS(String host, int port) {
		try {
			this.s = new Socket(host, port);
			os = s.getOutputStream();
			oos = new ObjectOutputStream(os);
			is = s.getInputStream();
			ois = new ObjectInputStream(is);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	public void disconnect() {
		try {
			os.close();
			oos.close();
			is.close();
			ois.close();
			s.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	public void handlePaymentDemand() {
		// Reception of the demand
		try {
			Receipt receipt = (Receipt)ois.readObject();
			// Ask for validation
			if (checkPaymentDemand(receipt)) {
				oos.writeObject(new Integer(1));
				// If demand accepted, then encrypt the bank info
				// with the nonce and the POS bank id
				Long sellerId = (Long)ois.readObject();
				Long nonce = (Long)ois.readObject();
				BankTicket bankTicket = new BankTicket(loadBankInfo(), nonce, sellerId);
				SecretKey key = generateKey();
				// Send AES encrypted bank ticket and RSA encrypted AES key
				EncryptedBankObject encryptedBankTicket = new EncryptedBankObject(
						BankInformation.encrypt(bankTicket, key), encryptKey(key, bankPublicKey));
				
				oos.writeObject(encryptedBankTicket);
			}
			else {
				// Demand rejected
				oos.writeObject(new Integer(0));
				disconnect();
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	
	public boolean checkPaymentDemand(Receipt receipt) {
		return true;
	}
	
	public SealedObject loadBankInfo() {
		SealedObject bankInformation = null;
		FileInputStream fis;
		try {
			fis = new FileInputStream("bank_information");
			ObjectInputStream ois = new ObjectInputStream(fis);
			bankInformation = (SealedObject)ois.readObject();
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return bankInformation;
	}
	
	public static void main(String[] args) {
		Client client = new Client();
		client.encryptAndStoreBankInfo("Timothée", "Lenel", 
				Long.valueOf("1234567890123456"), 123, new GregorianCalendar(2012, 12, 31));
		client.connectToPOS("localhost", 55555); // suppose the POS server is listening on this port
		client.handlePaymentDemand();
		client.disconnect();
	}

}
